/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file simpleHashMap.I
 * @author drose
 * @date 2007-07-19
 */

template<class Key, class Value, class Compare>
TypeHandle SimpleHashMap<Key, Value, Compare>::_type_handle;

/**
 *
 */
template<class Key, class Value, class Compare>
constexpr SimpleHashMap<Key, Value, Compare>::
SimpleHashMap(const Compare &comp) :
  _table(nullptr),
  _deleted_chain(nullptr),
  _table_size(0),
  _num_entries(0),
  _comp(comp)
{
}

/**
 *
 */
template<class Key, class Value, class Compare>
INLINE SimpleHashMap<Key, Value, Compare>::
SimpleHashMap(const SimpleHashMap &copy) :
  _table(nullptr),
  _deleted_chain(nullptr),
  _table_size(copy._table_size),
  _num_entries(copy._num_entries),
  _comp(copy._comp) {

  // We allocate enough bytes for _table_size elements of TableEntry, plus
  // _table_size * 4 more ints at the end (for the index array).
  if (_table_size > 0) {
    size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity);

    init_type();
    _deleted_chain = DeletedBufferChain::get_deleted_chain(alloc_size);
    _table = (TableEntry *)_deleted_chain->allocate(alloc_size, _type_handle);

    for (size_t i = 0; i < _num_entries; ++i) {
      new(&_table[i]) TableEntry(copy._table[i]);
    }

    // Copy the index array.
    memcpy(get_index_array(), copy.get_index_array(), _table_size * sizeof(int) * sparsity);
  }
}

/**
 *
 */
template<class Key, class Value, class Compare>
INLINE SimpleHashMap<Key, Value, Compare>::
SimpleHashMap(SimpleHashMap &&from) noexcept :
  _table(from._table),
  _deleted_chain(from._deleted_chain),
  _table_size(from._table_size),
  _num_entries(from._num_entries),
  _comp(std::move(from._comp))
{
  from._table = nullptr;
  from._deleted_chain = nullptr;
  from._table_size = 0;
  from._num_entries = 0;
}

/**
 *
 */
template<class Key, class Value, class Compare>
INLINE SimpleHashMap<Key, Value, Compare>::
~SimpleHashMap() {
  clear();
}

/**
 *
 */
template<class Key, class Value, class Compare>
INLINE SimpleHashMap<Key, Value, Compare> &SimpleHashMap<Key, Value, Compare>::
operator = (const SimpleHashMap<Key, Value, Compare> &copy) {
  if (this != &copy) {
    TableEntry *old_table = _table;
    DeletedBufferChain *old_deleted_chain = _deleted_chain;
    size_t old_num_entries = _num_entries;

    _table_size = copy._table_size;
    _num_entries = copy._num_entries;
    _comp = copy._comp;

    if (_table_size > 0) {
      // We allocate enough bytes for _table_size elements of TableEntry, plus
      // _table_size * 4 more ints at the end (for the index array).
      size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity);

      init_type();
      _deleted_chain = DeletedBufferChain::get_deleted_chain(alloc_size);
      _table = (TableEntry *)_deleted_chain->allocate(alloc_size, _type_handle);
      for (size_t i = 0; i < _num_entries; ++i) {
        new(&_table[i]) TableEntry(copy._table[i]);
      }

      // Copy the index array.
      memcpy(get_index_array(), copy.get_index_array(), _table_size * sizeof(int) * sparsity);
    } else {
      _table = nullptr;
      _deleted_chain = nullptr;
    }

    if (old_table != nullptr) {
      for (size_t i = 0; i < old_num_entries; ++i) {
        old_table[i].~TableEntry();
      }

      old_deleted_chain->deallocate(old_table, _type_handle);
    }
  }
  return *this;
}

/**
 *
 */
template<class Key, class Value, class Compare>
INLINE SimpleHashMap<Key, Value, Compare> &SimpleHashMap<Key, Value, Compare>::
operator = (SimpleHashMap<Key, Value, Compare> &&from) noexcept {
  if (this != &from) {
    _table = from._table;
    _deleted_chain = from._deleted_chain;
    _table_size = from._table_size;
    _num_entries = from._num_entries;
    _comp = std::move(from._comp);

    from._table = nullptr;
    from._deleted_chain = nullptr;
    from._table_size = 0;
    from._num_entries = 0;
  }
}

/**
 * Quickly exchanges the contents of this map and the other map.
 */
template<class Key, class Value, class Compare>
INLINE void SimpleHashMap<Key, Value, Compare>::
swap(SimpleHashMap<Key, Value, Compare> &other) {
  TableEntry *t0 = _table;
  _table = other._table;
  other._table = t0;

  DeletedBufferChain *t1 = _deleted_chain;
  _deleted_chain = other._deleted_chain;
  other._deleted_chain = t1;

  size_t t2 = _table_size;
  _table_size = other._table_size;
  other._table_size = t2;

  size_t t3 = _num_entries;
  _num_entries = other._num_entries;
  other._num_entries = t3;
}

/**
 * Searches for the indicated key in the table.  Returns its index number if
 * it is found, or -1 if it is not present in the table.
 */
template<class Key, class Value, class Compare>
int SimpleHashMap<Key, Value, Compare>::
find(const Key &key) const {
  if (_table_size == 0) {
    // Special case: the table is empty.
    return -1;
  }

  int slot = find_slot(key);
  if (slot >= 0) {
    return get_index_array()[slot];
  } else {
    // The key is not in the table.
    return -1;
  }
}

/**
 * Records the indicated key/data pair in the map.  If the key was already
 * present, silently replaces it.  Returns the index at which it was stored.
 */
template<class Key, class Value, class Compare>
int SimpleHashMap<Key, Value, Compare>::
store(const Key &key, const Value &data) {
  if (_table_size == 0) {
    // Special case: the first key in an empty table.
    nassertr(_num_entries == 0, -1);
    new_table();
    int pos = store_new_element(get_hash(key), key, data);
#ifdef _DEBUG
    nassertr(validate(), pos);
#endif
    return pos;
  }
  consider_expand_table();

  const int *index_array = get_index_array();
  size_t hash = get_hash(key);
  int index = index_array[hash];
  if (index < 0) {
    // This element is not already in the map; add it.
    if (consider_expand_table()) {
      return store(key, data);
    }
    index = store_new_element(hash, key, data);
#ifdef _DEBUG
    nassertr(validate(), index);
#endif
    return index;
  }
  if (is_element(index, key)) {
    // This element is already in the map; replace the data at that key.
    set_data(index, data);
#ifdef _DEBUG
    nassertr(validate(), index);
#endif
    return index;
  }

  // There was some other key at the hashed slot.  That's a hash conflict.
  // Record this entry at a later position.
  size_t slot = next_hash(hash);
  while (slot != hash) {
    index = index_array[slot];
    if (index < 0) {
      if (consider_expand_table()) {
        return store(key, data);
      }
      index = store_new_element(slot, key, data);
#ifdef _DEBUG
      nassertr(validate(), index);
#endif
      return index;
    }
    if (is_element(index, key)) {
      set_data(index, data);
#ifdef _DEBUG
      nassertr(validate(), index);
#endif
      return index;
    }
    slot = next_hash(slot);
  }

  // Shouldn't get here unless _num_entries == _table_size, which shouldn't be
  // possible due to consider_expand_table().
  nassertr(false, -1);
  return -1;  // To satisfy compiler
}

/**
 * Removes the indicated key and its associated data from the table.  Returns
 * true if the key was removed, false if it was not present.
 *
 * Iterator safety:  To perform removal during iteration, revisit the element
 * at the current index if removal succeeds,  keeping in mind that the number
 * of elements has now shrunk by one.
 */
template<class Key, class Value, class Compare>
INLINE bool SimpleHashMap<Key, Value, Compare>::
remove(const Key &key) {
  if (_num_entries == 0) {
    // Special case: the table is empty.
    return false;
  }

  int *index_array = get_index_array();
  size_t slot = (size_t)find_slot(key);
  if (slot == (size_t)-1) {
    // It wasn't in the hash map.
    return false;
  }

  // Now remove this element.
  size_t last = _num_entries - 1;
  int index = index_array[slot];
  if ((size_t)index < _num_entries) {
    // Find the last element in the index array.
    int other_slot = find_slot(_table[last]._key);
    nassertr(other_slot != -1, false);
    nassertr(index_array[(size_t)other_slot] == (int)last, false);

    // Swap it with the last one, so that we don't get any gaps in the table
    // of entries.
    _table[(size_t)index] = std::move(_table[last]);
    index_array[(size_t)other_slot] = index;
  }

  _table[last].~TableEntry();
  _num_entries = last;

  // It's important that we do this after the second find_slot, above, since
  // it might otherwise fail due to the unexpected gap, since some indices may
  // not be at their ideal positions right now.
  index_array[slot] = -1;

  //if (consider_shrink_table()) {
  //  // No need to worry about that gap; resize_table() will rebuild the index.
  //  return true;
  //}

  // Now we have put a hole in the index array.  If there was a hash conflict
  // in the slot after this one, we have to move it down to close the hole.
  slot = next_hash(slot);
  index = index_array[slot];
  while (index >= 0) {
    size_t wants_slot = get_hash(_table[index]._key);
    if (wants_slot != slot) {
      // This one was a hash conflict; try to put it where it belongs.  We
      // can't just put it in n, since maybe it belongs somewhere after n.
      while (wants_slot != slot && index_array[wants_slot] >= 0) {
        wants_slot = next_hash(wants_slot);
      }
      if (wants_slot != slot) {
        // We just have to flip the slots in the index array; we can keep the
        // elements in the table where they are.
        index_array[wants_slot] = index;
        index_array[slot] = -1;
      }
    }

    // Continue until we encounter the next unused slot.  Until we do, we
    // can't be sure we've found all of the potential hash conflicts.
    slot = next_hash(slot);
    index = index_array[slot];
  }

#ifdef _DEBUG
  nassertr(validate(), true);
#endif
  return true;
}

/**
 * Completely empties the table.
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
clear() {
  if (_table_size != 0) {
    for (size_t i = 0; i < _num_entries; ++i) {
      _table[i].~TableEntry();
    }

    _deleted_chain->deallocate(_table, _type_handle);
    _table = nullptr;
    _deleted_chain = nullptr;
    _table_size = 0;
    _num_entries = 0;
  }
}

/**
 * Returns a modifiable reference to the data associated with the indicated
 * key, or creates a new data entry and returns its reference.
 */
template<class Key, class Value, class Compare>
INLINE Value &SimpleHashMap<Key, Value, Compare>::
operator [] (const Key &key) {
  int index = find(key);
  if (index == -1) {
    index = store(key, Value());
  }
  return modify_data(index);
}

/**
 * Returns the total number of entries in the table.  Same as get_num_entries.
 */
template<class Key, class Value, class Compare>
constexpr size_t SimpleHashMap<Key, Value, Compare>::
size() const {
  return _num_entries;
}

/**
 * Returns the key in the nth entry of the table.
 *
 * @param n should be in the range 0 <= n < size().
 */
template<class Key, class Value, class Compare>
INLINE const Key &SimpleHashMap<Key, Value, Compare>::
get_key(size_t n) const {
  nassertr(n < _num_entries, _table[n]._key);
  return _table[n]._key;
}

/**
 * Returns the data in the nth entry of the table.
 *
 * @param n should be in the range 0 <= n < size().
 */
template<class Key, class Value, class Compare>
INLINE const Value &SimpleHashMap<Key, Value, Compare>::
get_data(size_t n) const {
  nassertr(n < _num_entries, _table[n].get_data());
  return _table[n].get_data();
}

/**
 * Returns a modifiable reference to the data in the nth entry of the table.
 *
 * @param n should be in the range 0 <= n < size().
 */
template<class Key, class Value, class Compare>
INLINE Value &SimpleHashMap<Key, Value, Compare>::
modify_data(size_t n) {
  nassertr(n < _num_entries, _table[n].modify_data());
  return _table[n].modify_data();
}

/**
 * Changes the data for the nth entry of the table.
 *
 * @param n should be in the range 0 <= n < size().
 */
template<class Key, class Value, class Compare>
INLINE void SimpleHashMap<Key, Value, Compare>::
set_data(size_t n, const Value &data) {
  nassertv(n < _num_entries);
  _table[n].set_data(data);
}

/**
 * Changes the data for the nth entry of the table.
 *
 * @param n should be in the range 0 <= n < size().
 */
template<class Key, class Value, class Compare>
INLINE void SimpleHashMap<Key, Value, Compare>::
set_data(size_t n, Value &&data) {
  nassertv(n < _num_entries);
  _table[n].set_data(std::move(data));
}

/**
 * Removes the nth entry from the table.
 *
 * @param n should be in the range 0 <= n < size().
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
remove_element(size_t n) {
  nassertv(n < _num_entries);
  remove(_table[n]._key);
}

/**
 * Returns the number of active entries in the table.  Same as size().
 */
template<class Key, class Value, class Compare>
INLINE size_t SimpleHashMap<Key, Value, Compare>::
get_num_entries() const {
  return _num_entries;
}

/**
 * Returns true if the table is empty; i.e. get_num_entries() == 0.
 */
template<class Key, class Value, class Compare>
INLINE bool SimpleHashMap<Key, Value, Compare>::
is_empty() const {
  return (_num_entries == 0);
}

/**
 *
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
output(std::ostream &out) const {
  out << "SimpleHashMap (" << _num_entries << " entries): [";
  const int *index_array = get_index_array();
  size_t num_slots = _table_size * sparsity;
  for (size_t slot = 0; slot < num_slots; ++slot) {
    if (!has_slot(slot)) {
      out << " *";

    } else {
      size_t index = (size_t)index_array[slot];
      out << " " << index;
      size_t ideal_slot = get_hash(_table[index]._key);
      if (ideal_slot != slot) {
        // This was misplaced as the result of a hash conflict.  Report how
        // far off it is.
        out << "(" << ((_table_size + slot - ideal_slot) & (num_slots - 1)) << ")";
      }
    }
  }
  out << " ]";
}

/**
 *
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
write(std::ostream &out) const {
  output(out);
  out << "\n";
  for (size_t i = 0; i < _num_entries; ++i) {
    out << "  " << _table[i]._key << " (hash " << get_hash(_table[i]._key) << ")\n";
  }
}

/**
 * Returns true if the internal table appears to be consistent, false if there
 * are some internal errors.
 */
template<class Key, class Value, class Compare>
bool SimpleHashMap<Key, Value, Compare>::
validate() const {
  size_t count = 0;

  const int *index_array = get_index_array();
  size_t num_slots = _table_size * sparsity;
  for (size_t slot = 0; slot < num_slots; ++slot) {
    if (has_slot(slot)) {
      size_t index = (size_t)index_array[slot];
      ++count;
      if (index >= _num_entries) {
        write(util_cat->error()
          << "SimpleHashMap " << this << " is invalid: slot " << slot
          << " contains index " << index << " which is past the end of the"
             " table\n");
        return false;
      }
      nassertd(index < _num_entries) continue;
      size_t ideal_slot = get_hash(_table[index]._key);
      size_t wants_slot = ideal_slot;
      while (wants_slot != slot && has_slot(wants_slot)) {
        wants_slot = next_hash(wants_slot);
      }
      if (wants_slot != slot) {
        write(util_cat->error()
          << "SimpleHashMap " << this << " is invalid: key "
          << _table[index]._key << " should be in slot " << wants_slot
          << " instead of " << slot << " (ideal is " << ideal_slot << ")\n");
        return false;
      }
    }
  }

  if (count != _num_entries) {
    write(util_cat->error()
      << "SimpleHashMap " << this << " is invalid: reports " << _num_entries
      << " entries, actually has " << count << "\n");
    return false;
  }

  return true;
}

/**
 * Computes an appropriate index number to store the given pointer.
 */
template<class Key, class Value, class Compare>
INLINE size_t SimpleHashMap<Key, Value, Compare>::
get_hash(const Key &key) const {
  /*
  // We want a hash constant 0 < k < 1.  This one is suggested by Knuth:
  static const double hash_constant = (sqrt(5.0) - 1.0) / 2.0;
  double f = ((double)_comp(key) * hash_constant);
  f -= floor(f);
  return (size_t)floor(f * _table_size);
  */

  return ((_comp(key) * (size_t)9973) >> 8) & ((_table_size * sparsity) - 1);
}

/**
 * Given a hash value, increments it, looping around the hash space.
 */
template<class Key, class Value, class Compare>
INLINE size_t SimpleHashMap<Key, Value, Compare>::
next_hash(size_t hash) const {
  return (hash + 1) & ((_table_size * sparsity) - 1);
}

/**
 * Finds the slot in which the given key should fit.
 */
template<class Key, class Value, class Compare>
INLINE int SimpleHashMap<Key, Value, Compare>::
find_slot(const Key &key) const {
  const int *index_array = get_index_array();
  size_t hash = get_hash(key);
  int index = index_array[hash];
  if (index < 0) {
    return -1;
  }

  if (is_element((size_t)index, key)) {
    return hash;
  }

  // There was some other key at the hashed slot.  That's a hash conflict.
  // Maybe our entry was recorded at a later slot position; scan the
  // subsequent positions until we find the entry or an unused slot,
  // indicating the end of the scan.
  size_t slot = next_hash(hash);
  while (slot != hash && has_slot(slot)) {
    if (is_element((size_t)index_array[slot], key)) {
      return (int)slot;
    }
    slot = next_hash(slot);
  }

  return -1;
}

/**
 * Returns true if the given slot refers to an element.
 */
template<class Key, class Value, class Compare>
INLINE bool SimpleHashMap<Key, Value, Compare>::
has_slot(size_t slot) const {
  return get_index_array()[slot] >= 0;
}

/**
 * Returns true if element n matches key.
 */
template<class Key, class Value, class Compare>
INLINE bool SimpleHashMap<Key, Value, Compare>::
is_element(size_t n, const Key &key) const {
  nassertr(n < _num_entries, false);
  return _comp.is_equal(_table[n]._key, key);
}

/**
 * Constructs a new TableEntry with the given slot, storing the indicated key
 * and value.
 */
template<class Key, class Value, class Compare>
INLINE size_t SimpleHashMap<Key, Value, Compare>::
store_new_element(size_t slot, const Key &key, const Value &data) {
  size_t index = _num_entries++;
  new(&_table[index]) TableEntry(key, data);
  nassertr(get_index_array()[slot] == -1, index)
  get_index_array()[slot] = index;
  return index;
}

/**
 * Returns the beginning of the array of _table_size ints that are the indices
 * pointing to the location within the table where the elements are stored.
 * within the table.
 */
template<class Key, class Value, class Compare>
INLINE int *SimpleHashMap<Key, Value, Compare>::
get_index_array() const {
  return (int *)(_table + _table_size);
}

/**
 * Allocates a brand new table.
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
new_table() {
  nassertv(_table_size == 0 && _num_entries == 0);

  // Pick a good initial table size.  For now, we make it really small.  Maybe
  // that's the right answer.
  _table_size = 2;

  // We allocate enough bytes for _table_size elements of TableEntry, plus
  // _table_size * 4 more ints at the end (for the index array).
  size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity);

  init_type();
  _deleted_chain = DeletedBufferChain::get_deleted_chain(alloc_size);
  _table = (TableEntry *)_deleted_chain->allocate(alloc_size, _type_handle);
  memset(get_index_array(), -1, _table_size * sizeof(int) * sparsity);
}

/**
 * Expands the table if it will need it (assuming one more element is about to
 * be added).  Returns true if expanded, false otherwise.
 */
template<class Key, class Value, class Compare>
INLINE bool SimpleHashMap<Key, Value, Compare>::
consider_expand_table() {
  if (_num_entries < _table_size) {
    return false;
  } else {
    resize_table(_table_size << 1);
    return true;
  }
}

/**
 * Shrinks the table if the allocated storage is significantly larger than the
 * number of elements in it.  Returns true if shrunk, false otherwise.
 */
template<class Key, class Value, class Compare>
INLINE bool SimpleHashMap<Key, Value, Compare>::
consider_shrink_table() {
  // If the number of elements gets less than an eighth of the table size, we
  // know it's probably time to shrink it down.
  if (_table_size <= 16 || _num_entries >= (_table_size >> 3)) {
    return false;
  } else {
    size_t new_size = _table_size;
    do {
      new_size >>= 1;
    } while (new_size >= 16 && _num_entries < (new_size >> 2));
    resize_table(new_size);
    return true;
  }
}

/**
 * Resizes the existing table.
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
resize_table(size_t new_size) {
  nassertv(_table_size != 0);
  nassertv(new_size >= _num_entries);

  DeletedBufferChain *old_chain = _deleted_chain;
  TableEntry *old_table = _table;

  _table_size = new_size;

  // We allocate enough bytes for _table_size elements of TableEntry, plus
  // _table_size * sparsity more ints at the end (for the sparse index array).
  size_t alloc_size = _table_size * sizeof(TableEntry) + _table_size * sparsity * sizeof(int);
  _deleted_chain = DeletedBufferChain::get_deleted_chain(alloc_size);
  _table = (TableEntry *)_deleted_chain->allocate(alloc_size, _type_handle);
  int *index_array = get_index_array();
  memset(index_array, -1, _table_size * sizeof(int) * sparsity);

  // Now copy the entries from the old table into the new table.  We don't
  // have to reorder these, fortunately.  Hopefully, a smart compiler will
  // optimize this to a memcpy.
  for (size_t i = 0; i < _num_entries; ++i) {
    new(&_table[i]) TableEntry(std::move(old_table[i]));
    old_table[i].~TableEntry();
  }

  // We don't need this old thing anymore.
  old_chain->deallocate(old_table, _type_handle);

  // Reindex the table.
  for (size_t i = 0; i < _num_entries; ++i) {
    size_t slot = get_hash(_table[i]._key);

    while (has_slot(slot)) {
      // Hash conflict;  look for a better spot.  This has to succeed.
      slot = next_hash(slot);
    }
    index_array[slot] = (int)i;
  }

  nassertv(validate());
}

/**
 *
 */
template<class Key, class Value, class Compare>
void SimpleHashMap<Key, Value, Compare>::
init_type() {
#if defined(HAVE_RTTI) && !defined(__EDG__)
  // If we have RTTI, we can determine the name of the base type.
  std::string key_name = typeid(Key).name();
  std::string value_name = typeid(Value).name();

  _type_handle =
    register_dynamic_type("SimpleHashMap<" + key_name + ", " + value_name + ">");
#else
  _type_handle =
    register_dynamic_type("SimpleHashMap<unknown, unknown>");
#endif
}
